INSTALLATION AND LOADING
To install sf from CRAN:
install.packages("sf")
or the development version from GitHub:
devtools::install_github("edzer/sfr")
then load:
library(sf)
NB MacOSX and LINUX users need to install a number of geospatial libraries (GEOS, GDAL, and proj.4).
The tidyverse package also needs to be loaded:
library(tidyverse)
EXAMPLE DATA
A GeoJSON of Greater Manchester’s wards was created from a vector boundary file available from ONS’s Open Geography Portal. The GeoJSON is projected in British National Grid (EPSG:27700) and originally derives from the Ordnance Survey.
Point data are supplied by data.police.uk and represent incidents of anti-social behaviour and crime recorded by the Greater Manchester Police during February 2017. The incidents are supplied with latitude and longitude coordinates which have undergone an anonymisation process.
READING AND WRITING SPATIAL DATA
Reading spatial data (polygons)
bdy <- st_read("data/wards.geojson", quiet = TRUE)
Reading data with coordinates (points)
pts <- read_csv("data/2017-03-greater-manchester-street.csv")
pts <- st_as_sf(pts, coords = c("Longitude", "Latitude"))
Writing spatial data
st_write(bdy, "boundaries.shp")
SF OBJECTS
Attribute table (dataframe) AND geometry (list-column) with coordinates, CRS, bbox
class(bdy)
[1] "sf" "data.frame"
head(bdy)
Simple feature collection with 6 features and 12 fields
geometry type: POLYGON
dimension: XY
bbox: xmin: 351664 ymin: 394521.5 xmax: 369078.9 ymax: 407481
epsg (SRID): 27700
proj4string: +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs
objectid wd16cd wd16nm wd16nmw lad16cd lad16nm bng_e bng_n long lat
1 772 E05000851 Ince <NA> E08000010 Wigan 359959 405278 -2.60570 53.54262
2 773 E05000852 Leigh East <NA> E08000010 Wigan 367298 400695 -2.49447 53.50194
3 774 E05000853 Leigh South <NA> E08000010 Wigan 366260 398660 -2.50990 53.48358
4 775 E05000854 Leigh West <NA> E08000010 Wigan 363428 400762 -2.55282 53.50228
5 776 E05000855 Lowton East <NA> E08000010 Wigan 362642 397699 -2.56431 53.47470
6 777 E05000856 Orrell <NA> E08000010 Wigan 353321 404083 -2.70568 53.53133
st_areasha st_lengths geometry
1 4780173 11442.63 POLYGON((359198.460435328 4...
2 4972092 11056.20 POLYGON((367700.613520717 4...
3 6521214 12377.97 POLYGON((364489.980933077 3...
4 7315344 16159.92 POLYGON((363107.910792193 3...
5 11185415 17990.73 POLYGON((361792.312268886 3...
6 8884317 19650.85 POLYGON((353418.76434581 40...
as.tibble(bdy)
bdy_df <- st_set_geometry(bdy, NULL) # remove geometry
head(bdy_df)
st_geometry(bdy) # print geometry
Geometry set for 215 features
geometry type: POLYGON
dimension: XY
bbox: xmin: 351664 ymin: 381168.6 xmax: 406087.5 ymax: 421039.8
epsg (SRID): 27700
proj4string: +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs
First 5 geometries:
MANIPULATE USING DPLYR
Rename variables
bdy <- bdy %>%
select(ward = wd16nm, census_code = wd16cd, borough = lad16nm) # rename variables
glimpse(bdy)
Observations: 215
Variables: 4
$ ward <fctr> Ince, Leigh East, Leigh South, Leigh West, Lowton East, Orrell, Pemberton,...
$ census_code <fctr> E05000851, E05000852, E05000853, E05000854, E05000855, E05000856, E0500085...
$ borough <fctr> Wigan, Wigan, Wigan, Wigan, Wigan, Wigan, Wigan, Wigan, Wigan, Wigan, Wiga...
$ geometry <simple_feature> POLYGON((359198.460435328 4..., POLYGON((367700.613520717 4..., ...
Count frequency of wards by borough
bdy %>%
group_by(borough) %>%
count() %>%
arrange(desc(n)) %>% # sort in descending order
st_set_geometry(., NULL) # hide geometry
Select features
chorlton <- bdy %>%
filter(ward == "Chorlton")
plot(st_geometry(bdy))
plot(st_geometry(chorlton), col = "red", add = TRUE)

Using sf functions to add geometry column to dplyr chain
bdy <- bdy %>%
mutate(area = st_area(.)) # returns the area of a feature
bdy %>%
select(ward, area) %>%
arrange(desc(area)) %>%
slice(1:10) %>%
st_set_geometry(., NULL)
PROJECTION
Check and assign CRS
st_crs(pts)
$epsg
[1] NA
$proj4string
[1] NA
attr(,"class")
[1] "crs"
pts <- st_set_crs(pts, 4326) # assign Lat/Long (epsg:4326)
st_crs(pts)
$epsg
[1] 4326
$proj4string
[1] "+proj=longlat +datum=WGS84 +no_defs"
attr(,"class")
[1] "crs"
Reproject CRS
bdy_WGS84 <- st_transform(bdy, 4326)
st_crs(bdy_WGS84)
$epsg
[1] 4326
$proj4string
[1] "+proj=longlat +datum=WGS84 +no_defs"
attr(,"class")
[1] "crs"
CONVERT TO AND FROM SP OBJECTS
Convert to and from sp objects
bdy_sp <- as(bdy, 'Spatial')
class(bdy_sp)
[1] "SpatialPolygonsDataFrame"
attr(,"package")
[1] "sp"
bdy_sf <- st_as_sf(bdy_sp)
class(bdy_sf)
[1] "sf" "data.frame"
SPATIAL OPERATIONS
Buffer features
buffer <- chorlton %>%
st_buffer(dist = 1000)
plot(st_geometry(buffer))
plot(st_geometry(chorlton), col = "red", add = TRUE)

Buffer and intersect
pts_sub <- bdy %>%
filter(ward == "Chorlton") %>%
st_buffer(dist = 1000) %>%
st_intersection(st_transform(pts, 27700)) # reproject pts to BNG (epsg:27700)
plot(st_geometry(buffer))
plot(st_geometry(chorlton), col = "red", add = TRUE)
plot(st_geometry(pts_sub), col = "black", add = TRUE)

pts_sub %>%
group_by(`Crime type`) %>%
count() %>%
arrange(desc(n)) %>%
st_set_geometry(., NULL)
Points in polygon
pts %>%
filter(`Crime type` == "Vehicle crime") %>%
st_join(bdy_WGS84, ., left = FALSE) %>%
count(ward) %>%
arrange(desc(n)) %>%
st_set_geometry(., NULL)
bdy_pts <- pts %>%
filter(`Crime type` == "Vehicle crime") %>%
st_join(bdy_WGS84, ., left = FALSE) %>%
count(ward)
PLOTTING
Base plots
plot(bdy_pts) # plots small multiples if dataframe has several attributes

plot(bdy_pts["n"]) # select the appropriate attribute to plot a single map

library(RColorBrewer) ; library(classInt)
pal <- brewer.pal(5, "RdPu")
classes <- classIntervals(bdy_pts$n, n=5, style="pretty")$brks
plot(bdy_pts["n"],
col = pal[findInterval(bdy_pts$n, classes, all.inside=TRUE)],
main = "Vehicle crime in Greater Manchester\nMarch 2017", axes = F)
legend("bottomright", legend = paste("<", round(classes[-1])), fill = pal, cex = 0.7)

ggplot2
devtools::install_github("tidyverse/ggplot2") # NB need development version for geom_sf()
ggplot(bdy_pts) +
geom_sf(aes(fill = n)) +
scale_fill_gradientn('Frequency', colours=RColorBrewer::brewer.pal(5,"RdPu"),
breaks = scales::pretty_breaks(n = 5)) +
labs(fill = "Frequency",
title = "Vehicle crime",
subtitle = "March 2017",
caption = "Contains OS data © Crown copyright and database right (2017)") +
theme_void()

plotly
library(plotly)
p <- ggplot(bdy_pts) +
geom_sf(aes(fill = n, text = paste0(ward, "\n", "Crimes: ", n))) +
scale_fill_gradientn('Frequency', colours=RColorBrewer::brewer.pal(5,"RdPu"),
breaks = scales::pretty_breaks(n = 5)) +
labs(fill = "Frequency",
title = "Vehicle crime",
subtitle = "March 2017",
caption = "Contains OS data © Crown copyright and database right (2017)") +
theme_void() +
coord_fixed(1.3)
ggplotly(p, tooltip = "text")
leaflet
library(leaflet)
pal <- colorBin("RdPu", domain = bdy_pts$n, bins = 5, pretty = TRUE)
leaflet(bdy_pts) %>%
addTiles(urlTemplate = "http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png",
attribution = '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, <a href="https://www.ons.gov.uk/methodology/geography/licences">Contains OS data © Crown copyright and database right (2017)</a>') %>%
addPolygons(fillColor = ~pal(n), fillOpacity = 0.8,
weight = 1, opacity = 1, color = "black",
label = ~as.character(ward)) %>%
addLegend(pal = pal, values = ~n, opacity = 0.7,
title = 'Vehicle crime (March 2017)', position = "bottomleft") %>%
addMiniMap(tiles = providers$CartoDB.Positron, toggleDisplay = TRUE)
LS0tCnRpdGxlOiAiU3BhdGlhbCBkYXRhIGluIFI6IGFuIGludHJvZHVjdGlvbiB0byB0aGUgc2YgcGFja2FnZSIKYXV0aG9yOiAiSGVucnkgUGFydHJpZGdlIgpkYXRlOiAiMjAxNy0wNi0wNSIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBmaWdfY2FwdGlvbjogbm8KICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDMKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KIyMgR2xvYmFsIGNvZGUgb3B0aW9ucwprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobz1UUlVFLAoJICAgICAgICAgICAgIGNhY2hlPVRSVUUsCiAgICAgICAgICAgICAgIHByb21wdD1GQUxTRSwKICAgICAgICAgICAgICAgdGlkeT1UUlVFLAogICAgICAgICAgICAgICBjb21tZW50PU5BLAogICAgICAgICAgICAgICBtZXNzYWdlPUZBTFNFLAogICAgICAgICAgICAgICB3YXJuaW5nPUZBTFNFKQpgYGAKCioqKgoKIyMjIElOU1RBTExBVElPTiBBTkQgTE9BRElORwoKVG8gaW5zdGFsbCBbc2ZdKGh0dHBzOi8vY3Jhbi5yc3R1ZGlvLmNvbS93ZWIvcGFja2FnZXMvc2YvKSBmcm9tIENSQU46CmBgYHtyIGV2YWw9RkFMU0V9Cmluc3RhbGwucGFja2FnZXMoInNmIikgIApgYGAKCm9yIHRoZSBkZXZlbG9wbWVudCB2ZXJzaW9uIGZyb20gR2l0SHViOgpgYGB7ciBldmFsPUZBTFNFfQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImVkemVyL3NmciIpCmBgYAoKdGhlbiBsb2FkOgpgYGB7cn0KbGlicmFyeShzZikKYGBgCgoqKk5CKiogTWFjT1NYIGFuZCBMSU5VWCB1c2VycyBuZWVkIHRvIGluc3RhbGwgYSBudW1iZXIgb2YgZ2Vvc3BhdGlhbCBsaWJyYXJpZXMgKEdFT1MsIEdEQUwsIGFuZCBwcm9qLjQpLiAgICAgCgpUaGUgW3RpZHl2ZXJzZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RpZHl2ZXJzZS9pbmRleC5odG1sKSBwYWNrYWdlIGFsc28gbmVlZHMgdG8gYmUgbG9hZGVkOgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKIyMjIEVYQU1QTEUgREFUQSAgICAgCkEgR2VvSlNPTiBvZiBHcmVhdGVyIE1hbmNoZXN0ZXIncyB3YXJkcyB3YXMgY3JlYXRlZCBmcm9tIGEgdmVjdG9yIGJvdW5kYXJ5IGZpbGUgYXZhaWxhYmxlIGZyb20gW09OUydzIE9wZW4gR2VvZ3JhcGh5IFBvcnRhbF0oaHR0cDovL2dlb3BvcnRhbC5zdGF0aXN0aWNzLmdvdi51ay9kYXRhc2V0cy9hZmNjODhhZmZlNWY0NTBlOWMwMzk3MGIyMzdhNzk5OV8yKS4gVGhlIEdlb0pTT04gaXMgcHJvamVjdGVkIGluIEJyaXRpc2ggTmF0aW9uYWwgR3JpZCAoRVBTRzoyNzcwMCkgYW5kIG9yaWdpbmFsbHkgZGVyaXZlcyBmcm9tIHRoZSBbT3JkbmFuY2UgU3VydmV5XShodHRwczovL3d3dy5vcmRuYW5jZXN1cnZleS5jby51ay9idXNpbmVzcy1hbmQtZ292ZXJubWVudC9wcm9kdWN0cy9vcGVuZGF0YS1wcm9kdWN0cy5odG1sKS4KClBvaW50IGRhdGEgYXJlIHN1cHBsaWVkIGJ5IFtkYXRhLnBvbGljZS51a10oaHR0cDovL2RhdGEucG9saWNlLnVrKSBhbmQgcmVwcmVzZW50IGluY2lkZW50cyBvZiBhbnRpLXNvY2lhbCBiZWhhdmlvdXIgYW5kIGNyaW1lIHJlY29yZGVkIGJ5IHRoZSBHcmVhdGVyIE1hbmNoZXN0ZXIgUG9saWNlIGR1cmluZyBGZWJydWFyeSAyMDE3LiBUaGUgaW5jaWRlbnRzIGFyZSBzdXBwbGllZCB3aXRoIGxhdGl0dWRlIGFuZCBsb25naXR1ZGUgY29vcmRpbmF0ZXMgd2hpY2ggaGF2ZSB1bmRlcmdvbmUgYW4gW2Fub255bWlzYXRpb24gcHJvY2Vzc10oaHR0cDovL2RhdGEucG9saWNlLnVrL2Fib3V0LyNhbm9ueW1pc2F0aW9uKS4KCiMjIyBSRUFESU5HIEFORCBXUklUSU5HIFNQQVRJQUwgREFUQQoKUmVhZGluZyBzcGF0aWFsIGRhdGEgKHBvbHlnb25zKQpgYGB7cn0KYmR5IDwtIHN0X3JlYWQoImRhdGEvd2FyZHMuZ2VvanNvbiIsIHF1aWV0ID0gVFJVRSkKYGBgCgpSZWFkaW5nIGRhdGEgd2l0aCBjb29yZGluYXRlcyAocG9pbnRzKQpgYGB7cn0KcHRzIDwtIHJlYWRfY3N2KCJkYXRhLzIwMTctMDMtZ3JlYXRlci1tYW5jaGVzdGVyLXN0cmVldC5jc3YiKQpwdHMgPC0gc3RfYXNfc2YocHRzLCBjb29yZHMgPSBjKCJMb25naXR1ZGUiLCAiTGF0aXR1ZGUiKSkKYGBgCgpXcml0aW5nIHNwYXRpYWwgZGF0YQpgYGB7ciBldmFsPUZBTFNFfQpzdF93cml0ZShiZHksICJib3VuZGFyaWVzLnNocCIpCmBgYAoKIyMjIFNGIE9CSkVDVFMKCkF0dHJpYnV0ZSB0YWJsZSAoZGF0YWZyYW1lKSBBTkQgZ2VvbWV0cnkgKGxpc3QtY29sdW1uKSB3aXRoIGNvb3JkaW5hdGVzLCBDUlMsIGJib3gKYGBge3J9CmNsYXNzKGJkeSkKaGVhZChiZHkpCmFzLnRpYmJsZShiZHkpCmJkeV9kZiA8LSBzdF9zZXRfZ2VvbWV0cnkoYmR5LCBOVUxMKSAjIHJlbW92ZSBnZW9tZXRyeQpoZWFkKGJkeV9kZikKc3RfZ2VvbWV0cnkoYmR5KSAjIHByaW50IGdlb21ldHJ5CmBgYAoKIyMjIE1BTklQVUxBVEUgVVNJTkcgRFBMWVIKClJlbmFtZSB2YXJpYWJsZXMKYGBge3J9CmJkeSA8LSBiZHkgJT4lIAogIHNlbGVjdCh3YXJkID0gd2QxNm5tLCBjZW5zdXNfY29kZSA9IHdkMTZjZCwgYm9yb3VnaCA9IGxhZDE2bm0pICMgcmVuYW1lIHZhcmlhYmxlcwpnbGltcHNlKGJkeSkKYGBgCgpDb3VudCBmcmVxdWVuY3kgb2Ygd2FyZHMgYnkgYm9yb3VnaApgYGB7cn0KYmR5ICU+JSAKICBncm91cF9ieShib3JvdWdoKSAlPiUgCiAgY291bnQoKSAlPiUgCiAgYXJyYW5nZShkZXNjKG4pKSAlPiUgIyBzb3J0IGluIGRlc2NlbmRpbmcgb3JkZXIKICBzdF9zZXRfZ2VvbWV0cnkoLiwgTlVMTCkgIyBoaWRlIGdlb21ldHJ5CmBgYAoKU2VsZWN0IGZlYXR1cmVzCmBgYHtyfQpjaG9ybHRvbiA8LSBiZHkgJT4lIAogIGZpbHRlcih3YXJkID09ICJDaG9ybHRvbiIpCnBsb3Qoc3RfZ2VvbWV0cnkoYmR5KSkKcGxvdChzdF9nZW9tZXRyeShjaG9ybHRvbiksIGNvbCA9ICJyZWQiLCBhZGQgPSBUUlVFKQpgYGAKClVzaW5nIHNmIGZ1bmN0aW9ucyB0byBhZGQgZ2VvbWV0cnkgY29sdW1uIHRvIGRwbHlyIGNoYWluCmBgYHtyfQpiZHkgPC0gYmR5ICU+JSAKICBtdXRhdGUoYXJlYSA9IHN0X2FyZWEoLikpICMgcmV0dXJucyB0aGUgYXJlYSBvZiBhIGZlYXR1cmUKCmJkeSAlPiUgCiAgc2VsZWN0KHdhcmQsIGFyZWEpICU+JSAKICBhcnJhbmdlKGRlc2MoYXJlYSkpICU+JSAKICBzbGljZSgxOjEwKSAlPiUgCiAgc3Rfc2V0X2dlb21ldHJ5KC4sIE5VTEwpCmBgYAoKIyMjIFBST0pFQ1RJT04KCkNoZWNrIGFuZCBhc3NpZ24gQ1JTCmBgYHtyfQpzdF9jcnMocHRzKSAKcHRzIDwtIHN0X3NldF9jcnMocHRzLCA0MzI2KSAjIGFzc2lnbiBMYXQvTG9uZyAoZXBzZzo0MzI2KQpzdF9jcnMocHRzKQpgYGAKClJlcHJvamVjdCBDUlMKYGBge3J9CmJkeV9XR1M4NCA8LSBzdF90cmFuc2Zvcm0oYmR5LCA0MzI2KQpzdF9jcnMoYmR5X1dHUzg0KQpgYGAKCiMjIyBDT05WRVJUIFRPIEFORCBGUk9NIFNQIE9CSkVDVFMKCkNvbnZlcnQgdG8gYW5kIGZyb20gc3Agb2JqZWN0cwpgYGB7cn0KYmR5X3NwIDwtIGFzKGJkeSwgJ1NwYXRpYWwnKQpjbGFzcyhiZHlfc3ApCmBgYAoKYGBge3J9CmJkeV9zZiA8LSBzdF9hc19zZihiZHlfc3ApCmNsYXNzKGJkeV9zZikKYGBgCgojIyMgU1BBVElBTCBPUEVSQVRJT05TCgpCdWZmZXIgZmVhdHVyZXMKYGBge3J9CmJ1ZmZlciA8LSBjaG9ybHRvbiAlPiUgCiAgc3RfYnVmZmVyKGRpc3QgPSAxMDAwKQpwbG90KHN0X2dlb21ldHJ5KGJ1ZmZlcikpCnBsb3Qoc3RfZ2VvbWV0cnkoY2hvcmx0b24pLCBjb2wgPSAicmVkIiwgYWRkID0gVFJVRSkKYGBgCgpCdWZmZXIgYW5kIGludGVyc2VjdApgYGB7cn0KcHRzX3N1YiA8LSBiZHkgJT4lCiAgZmlsdGVyKHdhcmQgPT0gIkNob3JsdG9uIikgJT4lCiAgc3RfYnVmZmVyKGRpc3QgPSAxMDAwKSAlPiUKICBzdF9pbnRlcnNlY3Rpb24oc3RfdHJhbnNmb3JtKHB0cywgMjc3MDApKSAjIHJlcHJvamVjdCBwdHMgdG8gQk5HIChlcHNnOjI3NzAwKQoKcGxvdChzdF9nZW9tZXRyeShidWZmZXIpKQpwbG90KHN0X2dlb21ldHJ5KGNob3JsdG9uKSwgY29sID0gInJlZCIsIGFkZCA9IFRSVUUpCnBsb3Qoc3RfZ2VvbWV0cnkocHRzX3N1YiksIGNvbCA9ICJibGFjayIsIGFkZCA9IFRSVUUpCmBgYAoKYGBge3J9CnB0c19zdWIgJT4lIAogIGdyb3VwX2J5KGBDcmltZSB0eXBlYCkgJT4lCiAgY291bnQoKSAlPiUgCiAgYXJyYW5nZShkZXNjKG4pKSAlPiUgCiAgc3Rfc2V0X2dlb21ldHJ5KC4sIE5VTEwpCmBgYAoKUG9pbnRzIGluIHBvbHlnb24KYGBge3J9CnB0cyAlPiUgCiAgZmlsdGVyKGBDcmltZSB0eXBlYCA9PSAiVmVoaWNsZSBjcmltZSIpICU+JSAgCiAgc3Rfam9pbihiZHlfV0dTODQsIC4sIGxlZnQgPSBGQUxTRSkgJT4lIAogIGNvdW50KHdhcmQpICU+JSAKICBhcnJhbmdlKGRlc2MobikpICU+JSAKICBzdF9zZXRfZ2VvbWV0cnkoLiwgTlVMTCkKCmJkeV9wdHMgPC0gcHRzICU+JSAKICBmaWx0ZXIoYENyaW1lIHR5cGVgID09ICJWZWhpY2xlIGNyaW1lIikgJT4lICAKICBzdF9qb2luKGJkeV9XR1M4NCwgLiwgbGVmdCA9IEZBTFNFKSAlPiUgCiAgY291bnQod2FyZCkKYGBgCgojIyMgUExPVFRJTkcKCkJhc2UgcGxvdHMKYGBge3J9CnBsb3QoYmR5X3B0cykgIyBwbG90cyBzbWFsbCBtdWx0aXBsZXMgaWYgZGF0YWZyYW1lIGhhcyBzZXZlcmFsIGF0dHJpYnV0ZXMKYGBgCgpgYGB7cn0KcGxvdChiZHlfcHRzWyJuIl0pICMgc2VsZWN0IHRoZSBhcHByb3ByaWF0ZSBhdHRyaWJ1dGUgdG8gcGxvdCBhIHNpbmdsZSBtYXAKYGBgCgpgYGB7cn0KbGlicmFyeShSQ29sb3JCcmV3ZXIpIDsgbGlicmFyeShjbGFzc0ludCkKcGFsIDwtIGJyZXdlci5wYWwoNSwgIlJkUHUiKQpjbGFzc2VzIDwtIGNsYXNzSW50ZXJ2YWxzKGJkeV9wdHMkbiwgbj01LCBzdHlsZT0icHJldHR5IikkYnJrcwpwbG90KGJkeV9wdHNbIm4iXSwgCiAgICAgY29sID0gcGFsW2ZpbmRJbnRlcnZhbChiZHlfcHRzJG4sIGNsYXNzZXMsIGFsbC5pbnNpZGU9VFJVRSldLCAKICAgICBtYWluID0gIlZlaGljbGUgY3JpbWUgaW4gR3JlYXRlciBNYW5jaGVzdGVyXG5NYXJjaCAyMDE3IiwgYXhlcyA9IEYpCmxlZ2VuZCgiYm90dG9tcmlnaHQiLCBsZWdlbmQgPSBwYXN0ZSgiPCIsIHJvdW5kKGNsYXNzZXNbLTFdKSksIGZpbGwgPSBwYWwsIGNleCA9IDAuNykgCmBgYAoKW2dncGxvdDJdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nZ3Bsb3QyL2luZGV4Lmh0bWwpCmBgYHtyLCBldmFsPUZBTFNFfQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoInRpZHl2ZXJzZS9nZ3Bsb3QyIikgIyBOQiBuZWVkIGRldmVsb3BtZW50IHZlcnNpb24gZm9yIGdlb21fc2YoKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoYmR5X3B0cykgKwogIGdlb21fc2YoYWVzKGZpbGwgPSBuKSkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKCdGcmVxdWVuY3knLCBjb2xvdXJzPVJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCg1LCJSZFB1IiksIAogICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNjYWxlczo6cHJldHR5X2JyZWFrcyhuID0gNSkpICsKICBsYWJzKGZpbGwgPSAiRnJlcXVlbmN5IiwKICAgICAgIHRpdGxlID0gIlZlaGljbGUgY3JpbWUiLAogICAgICAgc3VidGl0bGUgPSAiTWFyY2ggMjAxNyIsCiAgICAgICBjYXB0aW9uID0gIkNvbnRhaW5zIE9TIGRhdGEgwqkgQ3Jvd24gY29weXJpZ2h0IGFuZCBkYXRhYmFzZSByaWdodCAoMjAxNykiKSArCiAgdGhlbWVfdm9pZCgpCmBgYAoKW3Bsb3RseV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3Bsb3RseS9pbmRleC5odG1sKQpgYGB7cn0KbGlicmFyeShwbG90bHkpCnAgPC0gZ2dwbG90KGJkeV9wdHMpICsKICBnZW9tX3NmKGFlcyhmaWxsID0gbiwgdGV4dCA9IHBhc3RlMCh3YXJkLCAiXG4iLCAiQ3JpbWVzOiAiLCBuKSkpICsKICBzY2FsZV9maWxsX2dyYWRpZW50bignRnJlcXVlbmN5JywgY29sb3Vycz1SQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoNSwiUmRQdSIpLCAKICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzY2FsZXM6OnByZXR0eV9icmVha3MobiA9IDUpKSArCiAgbGFicyhmaWxsID0gIkZyZXF1ZW5jeSIsCiAgICAgICB0aXRsZSA9ICJWZWhpY2xlIGNyaW1lIiwKICAgICAgIHN1YnRpdGxlID0gIk1hcmNoIDIwMTciLAogICAgICAgY2FwdGlvbiA9ICJDb250YWlucyBPUyBkYXRhIMKpIENyb3duIGNvcHlyaWdodCBhbmQgZGF0YWJhc2UgcmlnaHQgKDIwMTcpIikgKwogIHRoZW1lX3ZvaWQoKSArIAogIGNvb3JkX2ZpeGVkKDEuMykKZ2dwbG90bHkocCwgdG9vbHRpcCA9ICJ0ZXh0IikKYGBgCgpbbGVhZmxldF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2xlYWZsZXQvaW5kZXguaHRtbCkKYGBge3J9CmxpYnJhcnkobGVhZmxldCkKcGFsIDwtIGNvbG9yQmluKCJSZFB1IiwgZG9tYWluID0gYmR5X3B0cyRuLCBiaW5zID0gNSwgcHJldHR5ID0gVFJVRSkKbGVhZmxldChiZHlfcHRzKSAlPiUgCiAgYWRkVGlsZXModXJsVGVtcGxhdGUgPSAiaHR0cDovL3tzfS50aWxlcy53bWZsYWJzLm9yZy9idy1tYXBuaWsve3p9L3t4fS97eX0ucG5nIiwKICAgIGF0dHJpYnV0aW9uID0gJyZjb3B5OyA8YSBocmVmPSJodHRwOi8vd3d3Lm9wZW5zdHJlZXRtYXAub3JnL2NvcHlyaWdodCI+T3BlblN0cmVldE1hcDwvYT4sIDxhIGhyZWY9Imh0dHBzOi8vd3d3Lm9ucy5nb3YudWsvbWV0aG9kb2xvZ3kvZ2VvZ3JhcGh5L2xpY2VuY2VzIj5Db250YWlucyBPUyBkYXRhIMKpIENyb3duIGNvcHlyaWdodCBhbmQgZGF0YWJhc2UgcmlnaHQgKDIwMTcpPC9hPicpICU+JSAKICBhZGRQb2x5Z29ucyhmaWxsQ29sb3IgPSB+cGFsKG4pLCBmaWxsT3BhY2l0eSA9IDAuOCwKICAgICAgICAgICAgICB3ZWlnaHQgPSAxLCBvcGFjaXR5ID0gMSwgY29sb3IgPSAiYmxhY2siLAogICAgICAgICAgICAgIGxhYmVsID0gfmFzLmNoYXJhY3Rlcih3YXJkKSkgJT4lIAogIGFkZExlZ2VuZChwYWwgPSBwYWwsIHZhbHVlcyA9IH5uLCBvcGFjaXR5ID0gMC43LCAKICAgICAgICAgICAgdGl0bGUgPSAnVmVoaWNsZSBjcmltZSAoTWFyY2ggMjAxNyknLCBwb3NpdGlvbiA9ICJib3R0b21sZWZ0IikgJT4lCiAgYWRkTWluaU1hcCh0aWxlcyA9IHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uLCB0b2dnbGVEaXNwbGF5ID0gVFJVRSkKYGBg